#include "Setting.h"
#include <unistd.h>
#include <fstream>
#include "sys.h"
#include "cJSON.h"
#include "sound.h"

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define CONFIG_FILE "./tmp/sysconfig"
#define WIFI_SEARCK_CMD "iwlist wlan0 scan"
#define WIFI_STATE_CMD "iwconfig wlan0"
#define NET_GET_INFO_CMD "ifconfig wlan0"
#define NET_DOWN "ifconfig wlan0 down"
#define NET_UP "ifconfig wlan0 up"

#define WPA_ADD_NETWORK "wpa_cli -i wlan0 add_network"
#define WPA_SET_NETWORK "wpa_cli -i wlan0 set_network"
#define WPA_SELECT_NETWORK "wpa_cli -i wlan0 select_network"
#define WPA_ENABLE_NETWORK "wpa_cli -i wlan0 enable_network"
#define WPA_DISABLE_NETWORK "wpa_cli -i wlan0 disable_network"
#define WPA_STATUS "wpa_cli -i wlan0 status"
#define WPA_UDHCPC "udhcpc -i wlan0 -q"
#define WPA_SAVE_CONFIG "wpa_cli -i wlan0 save_config"

static const char *netInfoHeader[] =
    {
        "HWaddr ",
        "inet addr:",
        "RX packets:",
        "TX packets:",
        "RX bytes:",
        "TX bytes:",
};

Setting::Setting(function<void(void)> exitCb, mutex &UIMutex)
{
    threadExitFlag = false;
    uiMutex = &UIMutex;

    SettingUI::Operations uiOpts = {0};

    uiOpts.exitCb = exitCb;
    uiOpts.poweroff = std::bind(&Setting::poweroff, this);
    uiOpts.reboot = std::bind(&Setting::reboot, this);
    uiOpts.wlanScan = std::bind(&Setting::wlanScan, this);
    uiOpts.connectAp = std::bind(&Setting::wlanConnect, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
    uiOpts.changePwd = std::bind(&Setting::wlanChangePassword, this, std::placeholders::_1, std::placeholders::_2);
    uiOpts.disconnectAp = std::bind(&Setting::wlanDisconnect, this, std::placeholders::_1);
    uiOpts.getBrightness = sys_get_brightness;
    uiOpts.getVolume = snd_get_volume;
    uiOpts.getConfig = std::bind(&Setting::getConfig, this);
    uiOpts.saveConfig = std::bind(&Setting::saveConfig, this, std::placeholders::_1, std::placeholders::_2);

    uiOpts.setVolume = [this](int v)
    {
        snd_set_volume(v);
        config->volume = v;
        config->magic = 1;
    };

    uiOpts.setBrightness = [this](int v)
    {
        sys_set_brightness(v);
        config->brightness = v;
        config->magic = 1;
    };

    SettingUI::create(uiOpts);

    config = (SysConfigMem *)getShareMem(); // 获取共享内存 - 配置参数
    if (config == nullptr)
        exitCb();

    sys_set_brightness(config->brightness); // 设置背光

    pThread = new std::thread(&Setting::threadFunction, this);
}

Setting::~Setting()
{
    threadExitFlag = true;
    pThread->join();
    delete pThread;

    SettingUI::release();

    shmdt((void *)config);
}

/**
 *@brief 读取设置信息
 *@param config 保存设置信息
 *@return true - 读取到了配置信息  false - 缺省值配置信息
 */
void *Setting::getShareMem(void)
{
    void *pMem = nullptr;

    shmId = shmget((key_t)shmKey, sizeof(SysConfigMem), 0666 | IPC_CREAT);
    if (shmId == -1)
    {
        printf("shmget failed1\n");
        return nullptr;
    }

    // 将共享内存连接到当前进程的地址空间
    pMem = shmat(shmId, 0, 0);
    if (pMem == (void *)-1)
    {
        printf("shmat failed1\n");
        return nullptr;
    }

    int appNum = ((SysConfigMem *)pMem)->appNum; // 先获取app数目
    shmdt(pMem);                                 // 断开共享内存连接

    // 再次申请共享内存
    shmId = shmget((key_t)shmKey, sizeof(SysConfigMem) + appNum * sizeof(SysConfigMem::AppInfoMem), 0666 | IPC_CREAT);
    if (shmId == -1)
    {
        printf("shmget failed2\n");
        return nullptr;
    }

    // 将共享内存连接到当前进程的地址空间
    pMem = shmat(shmId, 0, 0);
    if (pMem == (void *)-1)
    {
        printf("shmat failed2\n");
        return nullptr;
    }

    return pMem;
}

/**
 *@brief 更新网络连接状态
 *@return 返回已连接的wifi ssid，未连接则返回"null"
 */
string &Setting::updateConnection(void)
{
    FILE *fp;
    char *buff = new char[2048];
    memset(buff, 0, 2048);
    connectedSSID = "null";

    do
    {
        fp = popen(WIFI_STATE_CMD, "r"); // 执行shell命令
        if (fp == NULL)
            break;

        fread(buff, 1, 2048, fp); // 读取shell命令返回内容
        string str(buff);

        size_t dataStart = 0;
        size_t dataEnd = 0;

        dataStart = str.find("ESSID:\"", dataEnd); // 寻找ssid字串
        if (dataStart != string::npos)
        {
            dataStart += strlen("ESSID:\"");
            dataEnd = str.find('\"', dataStart); // 寻找 "
            if (dataEnd != string::npos)
                connectedSSID = str.substr(dataStart, dataEnd - dataStart);
        }

    } while (0);

    delete[] buff;
    pclose(fp);

    return connectedSSID;
}

/**
 *@brief 更新网络信息并添加到ui显示
 *@return 成功返回true，失败返回false
 */
bool Setting::getNetInfo(void)
{
    FILE *fp;

    fp = popen(NET_GET_INFO_CMD, "r"); // 执行shell命令
    if (fp == NULL)
        return false;

    char *buff = new char[2048];
    fread(buff, 1, 2048, fp); // 读取shell命令返回内容
    pclose(fp);

    string str(buff);
    SettingUI::NetInfo *info = new SettingUI::NetInfo;

    size_t dataStart = 0;
    size_t dataEnd = 0;
    string dataStr;
    char *array[] = {info->address, info->ipAddr};
    int *value[] = {&info->rxPackets, &info->txPackets, &info->rxBytes, &info->txBytes};

    // 解析字符串
    for (int i = 0; i < 6; i++)
    {
        dataStart = str.find(netInfoHeader[i], dataEnd) + strlen(netInfoHeader[i]); // 寻找前导字串
        dataEnd = str.find(' ', dataStart);                                         // 寻找空格
        if (dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            if (i < 2)
                strcpy(array[i], dataStr.c_str());
            else
                *(value[i - 2]) = stoi(dataStr);
        }
    }
    strcpy(info->essid, connectedSSID.c_str());

    // 更新到UI界面
    uiMutex->lock();
    SettingUI::updateNetInfo(*info);
    uiMutex->unlock();

    delete info;
    delete[] buff;

    return true;
}

/**
 *@brief 搜索wifi并添加到wifi列表
 *@return 成功返回true，失败返回false
 */
bool Setting::wlanScanThreadFunction(void)
{
    FILE *fp;

    fp = popen(WIFI_SEARCK_CMD, "r"); // 执行shell命令
    if (fp == NULL)
        return false;

    char *buff = new char[1024 * 100]; // 1个wifi信息大约1kB
    fread(buff, 1, 1024 * 100, fp);    // 读取shell命令返回内容

    pclose(fp);

    string str(buff);
    SettingUI::WifiInfo *info = new SettingUI::WifiInfo;

    size_t dataStart = 0;
    size_t dataEnd = 0;
    string dataStr;

    // 解析字符串
    while ((dataStart = str.find("Cell", dataEnd)) != string::npos) // 寻找wifi cell
    {
        dataStart = str.find("Address: ", dataEnd) + strlen("Address: "); // 寻找物理地址字串
        dataEnd = str.find('\n', dataStart);                              // 寻找换行符
        if (dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            snprintf(info->address, sizeof(info->address), "%s", dataStr.c_str());
        }

        dataStart = str.find("Frequency:", dataEnd) + strlen("Frequency:"); // 寻找频段字串
        dataEnd = str.find('\n', dataStart);                                // 寻找换行符
        if (dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            snprintf(info->freq, sizeof(info->freq), "%s", dataStr.c_str());
        }

        dataStart = str.find("Quality=", dataEnd) + strlen("Quality="); // 寻找信号强度字串
        dataEnd = str.find("/70", dataStart);                           // 寻找单位
        if (dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            info->level = atoi(dataStr.c_str()) * 100 / 70;
        }

        dataStart = str.find("Encryption key:", dataEnd) + strlen("Encryption key:"); // 寻找密钥使能字串
        dataEnd = str.find('\n', dataStart);                                          // 寻找换行符
        if (dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            info->encryptionKey = dataStr == "on" ? true : false;
        }

        dataStart = str.find("ESSID:\"", dataEnd) + strlen("ESSID:\""); // 寻找ssid字串
        dataEnd = str.find('\"', dataStart);                            // 寻找 "
        if (dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            snprintf(info->essid, sizeof(info->essid), "%s", dataStr.c_str());
        }

        dataStart = str.find("Bit Rates:", dataEnd) + strlen("Bit Rates:"); // 寻找比特率字串
        dataEnd = str.find('\n', dataStart);                                // 寻找换行符
        if (dataEnd != string::npos)
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            snprintf(info->bitRates, sizeof(info->bitRates), "%s", dataStr.c_str());
        }

        string ssid = string(info->essid);
        string psk;

        info->id = wlanSearch(ssid, psk);                          // 寻找是否是已保存的wifi
        snprintf(info->psk, sizeof(info->psk), "%s", psk.c_str()); // 写入密码

        info->connected = connectedSSID == string(info->essid) ? true : false;

        // 添加到wifi列表

        uiMutex->lock();
        SettingUI::addWiFiList(*info);
        uiMutex->unlock();
    }

    delete info;
    delete[] buff;

    return true;
}

int Setting::threadFunction()
{
    usleep(50000);

    while (!threadExitFlag)
    {
        updateConnection(); // 更新连接wifi信息
        getNetInfo();       // 更新网络信息

        usleep(500000);
    }

    return 0;
}

/**
 *@brief 修改已保存的wifi密码
 *@param id 已保存的wifi id
 *@param psk 新的密码
 */
void Setting::wlanChangePassword(int id, const char *psk)
{
    if (psk == NULL)
        return;

    char *cmd = new char[256];

    sprintf(cmd, "%s %d psk \'\"%s\"\'", WPA_SET_NETWORK, id, psk);
    system(cmd); // 执行命令修改密码

    sprintf(cmd, "%s", WPA_SAVE_CONFIG);
    system(cmd); // 执行命令保存修改

    delete[] cmd;
}

/**
 *@brief 保存新的wifi
 *@param ssid wifi帐号
 *@param psk 有密码则设置为密码，无则设置为"none"
 *@return 添加成功返回id，添加失败返回-1
 */
int Setting::wlanAddNetwork(string &ssid, string &psk)
{
    FILE *fp;
    int id = -1;
    char *buff = new char[256];

    fp = popen(WPA_ADD_NETWORK, "r"); // 执行shell命令，添加新的wifi
    if (fp != NULL)
    {
        fread(buff, 1, 256, fp); // 读取shell命令返回内容
        pclose(fp);

        string str(buff);
        if (!str.empty())
            id = stoi(str); // 获取wifi id
    }

    printf("\n\nid=%d\n\n", id);

    if (id >= 0)
    {
        sprintf(buff, "%s %d ssid  \'\"%s\"\'", WPA_SET_NETWORK, id, ssid.c_str());
        system(buff); // 设置ssid

        if (psk != "none")
            sprintf(buff, "%s %d psk  \'\"%s\"\'", WPA_SET_NETWORK, id, psk.c_str());
        else
            sprintf(buff, "%s %d key_mgmt NONE", WPA_SET_NETWORK, id);

        system(buff); // 设置密码

        sprintf(buff, "%s %d scan_ssid  1", WPA_SET_NETWORK, id);
        system(buff); // 设置scan_ssid

        sprintf(buff, "%s", WPA_SAVE_CONFIG);
        system(buff); // 执行命令保存修改
    }

    delete[] buff;

    return id;
}

/**
 *@brief 扫描/etc/wpa_supplicant.conf文件是否是已保存的wifi
 *@param ssid wifi帐号
 *@param psk 有密码则会设置为密码，无则会设置为"none"
 *@return 匹配成功返回id，匹配失败返回-1
 */
int Setting::wlanSearch(string &ssid, string &psk)
{
    ifstream file;
    int id = 0;
    psk = "none";

    file.open("/etc/wpa_supplicant.conf", ios::in);

    if (file.is_open() != true)
    {
        id = -1;
        return id;
    }

    char *buf = (char *)calloc(40960, 1);
    file.read(buf, 40960);
    file.close();

    string str(buf);
    free(buf);

    size_t dataStart = 0;
    size_t dataEnd = 0;
    string dataStr;

    // 解析字符串
    while ((dataStart = str.find("network", dataEnd)) != string::npos) // 寻找network
    {
        dataStart = str.find("ssid=\"", dataEnd) + strlen("ssid=\""); // 寻找ssid字串
        dataEnd = str.find('\"', dataStart);                          // 寻找 "
        if (dataEnd != string::npos)                                  // 找到ssid
        {
            dataStr = str.substr(dataStart, dataEnd - dataStart);
            if (dataStr == ssid) // ssid匹配成功
            {
                dataStart = str.find("psk=\"", dataEnd) + strlen("psk=\""); // 寻找psk字串
                dataEnd = str.find('\"', dataStart);                        // 寻找 "
                if (dataEnd != string::npos)                                // 找到密码
                {
                    dataStr = str.substr(dataStart, dataEnd - dataStart);
                    psk = dataStr;
                }

                return id;
            }
        }

        id++;
    }

    return -1;
}

/**
 *@brief 关闭指定连接
 *@param id 已保存的wifi id，为-1则表示是全新的wifi
 */
void Setting::wlanDisconnect(int id)
{
    if (id < 0)
        return;

    char *cmd = new char[256];

    sprintf(cmd, "%s %d", WPA_DISABLE_NETWORK, id);
    system(cmd); // 执行命令关闭连接
    SettingUI::setConnectOver(false);

    delete[] cmd;
}

/**
 *@brief 连接指定的wifi
 *@param id 已保存的wifi id，为-1则表示是全新的wifi
 *@param ssid wifi帐号，只有id为-1使用到
 *@param psk 有密码则设置为密码，无则设置为"none"， 只有id为-1使用到
 *@return 匹配成功返回id，匹配失败返回-1
 */
void Setting::wlanConnectFunction(int id, const char *_ssid, const char *_psk)
{
    if (_ssid == NULL || _psk == NULL)
        return;

    if (id < 0) // 未保存的wifi
    {
        string ssid(_ssid);
        string psk(_psk);

        id = wlanAddNetwork(ssid, psk);
    }

    char *cmd = new char[64];

    uiMutex->lock();
    SettingUI::connectingContCreate(); // 通知UI开始连接
    uiMutex->unlock();

    sprintf(cmd, "%s %d", WPA_SELECT_NETWORK, id); // 连接热点
    system(cmd);

    sprintf(cmd, "%s %d", WPA_ENABLE_NETWORK, id); // 使能热点
    system(cmd);

    delete[] cmd;

    int tick = 0;
    char *buff = new char[4096];

    while (tick < 10) // 超时时间10 ×1s = 10s
    {
        FILE *fp;
        fp = popen(WPA_STATUS, "r"); // 执行shell命令，查询wifi连接状态
        if (fp == NULL)
            continue;

        memset(buff, 0, 4096);
        fread(buff, 1, 4096, fp); // 读取shell命令返回内容
        pclose(fp);

        string state(buff);
        size_t dataStart = 0;
        size_t dataEnd = 0;
        string dataStr;

        dataStart = state.find("wpa_state=", dataEnd) + strlen("wpa_state="); // 寻找state字串
        dataEnd = state.find('\n', dataStart);                                // 寻找换行符
        if (dataEnd != string::npos)                                          // 找到字段
        {
            dataStr = state.substr(dataStart, dataEnd - dataStart);
            if (dataStr == "COMPLETED") // 连接成功状态
                break;
        }
        sleep(1);
        tick++;
    }

    if (tick >= 10)
    {
        // 告知UI连接失败
        uiMutex->lock();
        SettingUI::setConnectStatus(false);
        SettingUI::setConnectOver(false); // 告知UI连接动作结束
        uiMutex->unlock();

        delete[] buff;
        return;
    }

    uiMutex->lock();
    SettingUI::setConnectStatus(true); // 告知UI连接成功
    uiMutex->unlock();

    FILE *fp;
    fp = popen(WPA_UDHCPC, "r"); // 执行shell命令，获取IP地址与dns地址
    if (fp == NULL)
    {
        // 告知获取IP地址失败
        uiMutex->lock();
        SettingUI::setGOTIPStatus(false);
        SettingUI::setConnectOver(false);
        uiMutex->unlock();

        delete[] buff;
        return;
    }
    memset(buff, 0, 4096);
    fread(buff, 1, 4096, fp); // 读取shell命令返回内容
    pclose(fp);

    string state(buff);
    printf("\nreceive:\n%s\n", state.c_str());
    size_t strIndex = 0;
    bool status = false;

    strIndex = state.find("setting default routers", 0); // 寻找获取IP地址前导字串
    uiMutex->lock();
    if (strIndex != string::npos) // 找到字段
    {
        SettingUI::setGOTIPStatus(true); // 告知UI获取IP地址成功
        status = true;
    }
    else
        SettingUI::setGOTIPStatus(false); // 告知UI获取IP地址失败

    SettingUI::setConnectOver(status); // 告知UI连接动作结束

    uiMutex->unlock();
    delete[] buff;
}

void Setting::wlanConnect(int id, const char *_ssid, const char *_psk)
{
    thread wlanConnectThread(&Setting::wlanConnectFunction, this, id, _ssid, _psk); // 创建线程连接wifi，不会导致UI卡死
    wlanConnectThread.detach();
}

void Setting::wlanScan(void)
{
    thread wlanScanThread(&Setting::wlanScanThreadFunction, this); // 创建线程搜索wifi，不会导致UI卡死

    wlanScanThread.detach(); // 线程分离
}

void Setting::poweroff(void)
{
    sys_set_brightness(0); // 关闭背光
    system("poweroff");
}

void Setting::reboot(void)
{
    sys_set_brightness(0); // 关闭背光
    system("reboot");
}

void Setting::getConfig(void)
{
    for (int i = 0; i < config->appNum; i++)
    {
        const SysConfigMem::AppInfoMem *appMem = &config->appInfo[i];

        string appName(appMem->name);
        SettingUI::addConfigPage(appName);

        // 解析json
        ifstream configFile;

        configFile.open(appMem->config, ios::in);

        if (configFile.is_open() != true)
            continue;

        char *buf = (char *)calloc(4096, 1);
        configFile.read(buf, 4096);
        configFile.close();

        cJSON *cjson = cJSON_Parse(buf);
        if (cjson != nullptr)
        {
            for (cJSON *c = cjson->child; c != nullptr; c = c->next) // 遍历json
            {
                if (strstr(c->string, "_zh") != nullptr)
                    continue;

                if (c->type != cJSON_Number && c->type != cJSON_String)
                    continue;

                SettingUI::ConfigItemInfo itemInfo;

                string itemNameZh = string(c->string) + "_zh";

                cJSON *zh = cJSON_GetObjectItem(cjson, itemNameZh.c_str());
                if (zh != nullptr && zh->type == cJSON_String)
                    itemInfo.nameZh = zh->valuestring;

                itemInfo.name = c->string;

                switch (c->type)
                {
                case cJSON_Number:
                    itemInfo.type = SettingUI::Config_Number;
                    itemInfo.value = c->valueint;
                    break;
                case cJSON_String:
                    itemInfo.type = SettingUI::Config_String;
                    itemInfo.string = c->valuestring;
                    break;
                default:;
                }

                SettingUI::addConfigItem(i, itemInfo);
            }

            cJSON_Delete(cjson);
        }
        free(buf);
    }
}

bool Setting::saveConfig(int pageIndex, vector<SettingUI::ConfigItemInfo> &itemVector)
{
    cJSON *cjson = cJSON_CreateObject();

    for (auto item : itemVector)
    {
        switch (item.type)
        {
        case SettingUI::Config_Number:
            cJSON_AddNumberToObject(cjson, item.name.c_str(), item.value);      // 添加数值对象
            break;
        case SettingUI::Config_String:
            cJSON_AddStringToObject(cjson, item.name.c_str(), item.string.c_str());     // 添加字符串对象
            break;
        default:;
        }

        if(item.nameZh != "")
        {
            string itemNameZh = item.name + "_zh";
            cJSON_AddStringToObject(cjson, itemNameZh.c_str(), item.nameZh.c_str());   // 添加中文说明字符串对象
        }
    }
    string jsonString(cJSON_Print(cjson));

    ofstream file;

    file.open(config->appInfo[pageIndex].config, ios::out); // 写方式打开文件

    file << jsonString << endl;

    file.close();

    cJSON_Delete(cjson);

    return true;
}
